#pragma once
#include "../mqcomm/mq_log.hpp"
#include "../mqcomm/mq_helper.hpp"
#include "../mqcomm/mq_msg.pb.h"

#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <cassert>
#include <algorithm>

namespace ns_kkymq
{
    struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string _exchange_name;
        std::string _msgqueue_name;
        std::string _binding_key;
        Binding() = default;
        Binding(std::string exchange_name, std::string msgqueue_name, std::string binding_key)
            : _exchange_name(exchange_name), _msgqueue_name(msgqueue_name), _binding_key(binding_key)
        {
        }
        ~Binding() {}
    };

    // 队列与绑定信息是一一对应的（因为是给某个交换机去绑定队列，因此一个交换机可能会有多个绑定信息）
    // 因此先定义队列名与绑定信息的映射关系，这是为了方便通过队列名来查找绑定信息
    using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
    // 然后定义交换机名称与队列绑定信息的映射关系。这个map中包含了所有的绑定信息，并以交换机为单元进行区分
    using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;

    class BindingMapper
    {
    public:
        BindingMapper(const std::string &dbfile)
            : _sql_helper(dbfile)
        {
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            _sql_helper.open();
            createTable();
        }
        void createTable()
        {
            // create table if not exists binding_table(exchange_name varchar(32),msgqueue_name,binding_key)
            std::stringstream sql;
            sql << "create table if not exists binding_table(";
            sql << "exchange_name varchar(32), ";
            sql << "msgqueue_name varchar(32), ";
            sql << "binding_key varchar(128));";
            assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        void removeTable()
        {
            std::string sql = "drop table if exists binding_table;";
            _sql_helper.exec(sql, nullptr, nullptr);
        }

        bool insert(Binding::ptr &binding)
        {
            // insert into binding_table values('exchange1','msgqueue1','new.music.#')
            std::stringstream sql;
            sql << "insert into binding_table values('";
            sql << binding->_exchange_name << "','";
            sql << binding->_msgqueue_name << "','";
            sql << binding->_binding_key << "')";
            return _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        void remove(const std::string &e_name, const std::string &q_name)
        {
            // delete from binding_table where exchange_name='' and msgqueue_name='';
            std::stringstream sql;
            sql << "delete from binding_table where exchange_name='";
            sql << e_name << "' and msgqueue_name='";
            sql << q_name << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        void removeExchangeBindings(const std::string &e_name)
        {
            // delete from binding_table where exchange_name='';
            std::stringstream sql;
            sql << "delete from binding_table where exchange_name='";
            sql << e_name << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        void removeMsgQueueBindings(const std::string q_name)
        {
            std::stringstream sql;
            sql << "delete from binding_table where msgqueue_name='";
            sql << q_name << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        BindingMap recovery()
        {
            // select exchange_name,msgqueue_name,binding_key from binding_table;
            BindingMap result;
            std::string sql = "select exchange_name,msgqueue_name,binding_key from binding_table;";
            _sql_helper.exec(sql, selectCallback, &result);
            return result;
        }

    private:
        static int selectCallback(void *arg, int num, char **row, char **fields)
        {
            BindingMap *result = (BindingMap *)arg;
            auto bp = std::make_shared<Binding>(row[0], row[1], row[2]);
            // 注意添加&获取到变量的原始数据
            // 为了防止 交换机相关的绑定信息已经存在，因此不能直接创建队列映射进行添加，这样会覆盖历史数据
            // 因此得先获取交换机对应的映射对象，往里边添加数据
            // 但是，若这个时候没有交换机对应的映射关系，因此这里的获取需要引用（保证不存在则自动创建）
            MsgQueueBindingMap &qmap = (*result)[bp->_exchange_name];
            // 这里对原始数据进行插入才对，如果是局部变量就没用
            qmap.insert(std::make_pair(bp->_msgqueue_name, bp));
            // result->insert({bp->_exchange_name,qmap});  不需要了 前面operator[]已经插入了
            return 0;
        }

    private:
        SqliteHelper _sql_helper;
    };

    class BindingManager
    {
    public:
        using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string &dbfile)
            : _mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }

        bool bind(const std::string &e_name, const std::string &q_name, const std::string key, bool durable)
        {
            // 加锁，构造一个队列的绑定信息对象，添加映射关系
            std::unique_lock<std::mutex> lock(_mtx);
            auto it = _bindings.find(e_name);
            if (it != _bindings.end() && it->second.find(q_name) != it->second.end())
            {
                // 绑定信息已经存在，不需要再操作了
                return true;
            }
            Binding::ptr bp = std::make_shared<Binding>(e_name, q_name, key);
            auto &qbmap = _bindings[e_name];
            qbmap.insert({q_name, bp});
            if (durable)
            {
                return _mapper.insert(bp);
            }
            return true;
        }

        void unBind(const std::string &e_name, const std::string &q_name)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto eit = _bindings.find(e_name);
            // 没有交换机相关的绑定信息
            if (eit == _bindings.end())
                return;
            auto qit = eit->second.find(q_name);
            // 没有队列相关的绑定信息
            if (qit == eit->second.end())
                return;
            _bindings[e_name].erase(q_name);
            _mapper.remove(e_name, q_name);
        }

        void removeExchangeBindings(const std::string &e_name)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _mapper.removeExchangeBindings(e_name);
            _bindings.erase(e_name);
        }

        void removeMagQueueBindings(const std::string &q_name)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _mapper.removeMsgQueueBindings(q_name);
            for (auto start = _bindings.begin(); start != _bindings.end(); ++start)
            {
                // 遍历每个交换机的绑定信息，从而移除指定队列的相关信息。
                start->second.erase(q_name);
            }
        }

        MsgQueueBindingMap getExchangeBindings(const std::string &e_name)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto eit = _bindings.find(e_name);
            if (eit == _bindings.end())
            {
                return MsgQueueBindingMap();
            }
            return eit->second;
        }

        Binding::ptr getBinding(const std::string &e_name, const std::string &q_name)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto eit = _bindings.find(e_name);
            if (eit == _bindings.end())
            {
                return Binding::ptr();
            }
            auto qit = eit->second.find(q_name);
            if (qit == eit->second.end())
            {
                return Binding::ptr();
            }
            return qit->second;
        }

        bool exists(const std::string &e_name, const std::string &q_name)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto eit = _bindings.find(e_name);
            if (eit == _bindings.end())
            {
                return false;
            }
            auto qit = eit->second.find(q_name);
            if (qit == eit->second.end())
            {
                return false;
            }
            return true;
        }

        size_t size()
        {
            size_t total_size = 0;
            std::unique_lock<std::mutex> lock(_mtx);
            for (auto start = _bindings.begin(); start != _bindings.end(); ++start)
            {
                // 遍历每个交换机的绑定信息，找到size进行添加
                total_size += start->second.size();
            }
            return total_size;
        }

        void clear()
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _mapper.removeTable();
            _bindings.clear();
        }

    private:
        std::mutex _mtx;
        BindingMapper _mapper;
        BindingMap _bindings;
    };
}